Use JBSMATCH opcode for ethernet address matching This commit refactors the code to use the JBSMATCH opcode when possible for ethernet address matching, improving code density. Before: 03-21 14:43:07.523 28553 28566 I ApfFilterTest: all feature on, program size: 4503 After: 03-21 16:43:22.941 1490 1510 I ApfFilterTest: all feature on, program size: 4446 Test: TH Change-Id: Id2f20bdfe2da579b3f1d8e441533532aaee2816d
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 7c6692f..bde487e 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -1830,8 +1830,8 @@ // Pass if non-broadcast reply. // This also accepts multicast arp, but we assume those don't exist. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_ARP_UNICAST_REPLY); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_ARP_UNICAST_REPLY); // It is a broadcast reply. if (mIPv4Address == null) { @@ -1897,8 +1897,8 @@ // the future, such packets will likely be dropped by multicast filters. // Since the device may have packet forwarding enabled, APF needs to pass any received // unicast IPv4 ping not destined for the device's IP address to the kernel. - gen.addLoadImmediate(R0, ETHER_DST_ADDR_OFFSET) - .addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipIpv4PingFilter) + gen.addJumpIfBytesAtOffsetNotEqual( + ETH_DEST_ADDR_OFFSET, mHardwareAddress, skipIpv4PingFilter) .addLoadImmediate(R0, IPV4_DEST_ADDR_OFFSET) .addJumpIfBytesAtR0NotEqual(mIPv4Address, skipIpv4PingFilter); @@ -1979,11 +1979,11 @@ // address for IPv4 mDNS packet) or the device's MAC address, skip filtering. // We need to check both the mDNS multicast MAC address and the device's MAC address // because multicast to unicast conversion might have occurred. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0EqualsNoneOf( - List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS), - skipMdnsFilter - ); + gen.addJumpIfBytesAtOffsetEqualsNoneOf( + ETH_DEST_ADDR_OFFSET, + List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS), + skipMdnsFilter + ); // Ignore packets with IPv4 options (header size not equal to 20) as they are rare. gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE) @@ -2185,8 +2185,8 @@ // Otherwise, this is an IPv4 unicast, pass // If L2 broadcast packet, drop. // TODO: can we invert this condition to fall through to the common pass case below? - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_IPV4_UNICAST); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_IPV4_UNICAST); gen.addCountAndDrop(DROPPED_IPV4_L2_BROADCAST); } @@ -2329,8 +2329,8 @@ // used by processes other than clatd. This is because APF cannot reliably detect signal // on when IPV6_{JOIN,LEAVE}_ANYCAST is triggered. final List<byte[]> allMACs = getKnownMacAddresses(); - v6Gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addCountAndDropIfBytesAtR0EqualsNoneOf(allMACs, DROPPED_IPV6_NS_OTHER_HOST); + v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf(ETH_DEST_ADDR_OFFSET, allMACs, + DROPPED_IPV6_NS_OTHER_HOST); // Dst IPv6 address check: final List<byte[]> allSuffixes = getSolicitedNodeMcastAddressSuffix(allIPv6Addrs); @@ -2444,11 +2444,11 @@ // address for IPv6 mDNS packet) or the device's MAC address, skip filtering. // We need to check both the mDNS multicast MAC address and the device's MAC address // because multicast to unicast conversion might have occurred. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0EqualsNoneOf( - List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS), - skipMdnsFilter - ); + gen.addJumpIfBytesAtOffsetEqualsNoneOf( + ETH_DEST_ADDR_OFFSET, + List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS), + skipMdnsFilter + ); // Skip filtering if the packet is not an IPv6 UDP packet. gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET) @@ -2509,8 +2509,8 @@ true /* includeNonTentative */, false /* includeTentative */, false /* includeAnycast */); - gen.addLoadImmediate(R0, ETHER_DST_ADDR_OFFSET) - .addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipPing6Offload) + gen.addJumpIfBytesAtOffsetNotEqual( + ETHER_DST_ADDR_OFFSET, mHardwareAddress, skipPing6Offload) .addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET) .addJumpIfBytesAtR0EqualsNoneOf(nonTentativeIPv6Addrs, skipPing6Offload); @@ -3604,8 +3604,8 @@ // Pass unicast TDLS packet but drop non-unicast TDLS packet. short skipTDLScheck = gen.getUniqueLabel(); gen.addJumpIfR0NotEquals(0x890DL, skipTDLScheck) - .addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addCountAndDropIfBytesAtR0NotEqual(mHardwareAddress, DROPPED_NON_UNICAST_TDLS) + .addCountAndDropIfBytesAtOffsetNotEqual( + ETH_DEST_ADDR_OFFSET, mHardwareAddress, DROPPED_NON_UNICAST_TDLS) .addCountAndPass(PASSED_NON_IP_UNICAST) .defineLabel(skipTDLScheck); @@ -3650,8 +3650,8 @@ gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel); // Drop non-IP non-ARP broadcasts, pass the rest - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_NON_IP_UNICAST); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_NON_IP_UNICAST); gen.addCountAndDrop(DROPPED_ETH_BROADCAST); // Add IPv6 filters: @@ -3739,11 +3739,17 @@ } void preloadData(ApfV61GeneratorBase<?> gen) throws IllegalInstructionException { + final List<byte[]> preloadedMacAddress = getKnownMacAddresses(); final List<byte[]> preloadedIPv6Address = getIpv6Addresses(true /* includeNonTentative */, true /* includeTentative */, true /* includeAnycast */); - final int preloadDataSize = preloadedIPv6Address.size() * 16; + final int preloadDataSize = + preloadedIPv6Address.size() * 16 + preloadedMacAddress.size() * 6; final byte[] preloadData = new byte[preloadDataSize]; int offset = 0; + for (byte[] addr : preloadedMacAddress) { + System.arraycopy(addr, 0, preloadData, offset, 6); + offset += 6; + } for (byte[] addr : preloadedIPv6Address) { System.arraycopy(addr, 0, preloadData, offset, 16); offset += 16;
diff --git a/src/android/net/apf/ApfV4GeneratorBase.java b/src/android/net/apf/ApfV4GeneratorBase.java index f142e31..fb929dc 100644 --- a/src/android/net/apf/ApfV4GeneratorBase.java +++ b/src/android/net/apf/ApfV4GeneratorBase.java
@@ -482,6 +482,17 @@ } /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0NotEqual(bytes, tgt); + } + + /** * Add instructions to the end of the program to increase counter and drop packet if the * bytes of the packet at an offset specified by register0 don't match {@code bytes}. * WARNING: may modify R1 @@ -499,6 +510,28 @@ /** * Add instructions to the end of the program to increase counter and drop packet if the + * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0NotEqual(bytes, cnt); + } + + /** + * Add instructions to the end of the program to increase counter and pass packet if the + * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0NotEqual(bytes, cnt); + } + + /** + * Add instructions to the end of the program to increase counter and drop packet if the * bytes of the packet at an offset specified by register0 match {@code bytes}. * WARNING: may modify R1 */
diff --git a/src/android/net/apf/ApfV61GeneratorBase.java b/src/android/net/apf/ApfV61GeneratorBase.java index 014d893..c686b71 100644 --- a/src/android/net/apf/ApfV61GeneratorBase.java +++ b/src/android/net/apf/ApfV61GeneratorBase.java
@@ -325,6 +325,24 @@ return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpPassLabel()); } + @Override + public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpPassLabel()); + } + + @Override + public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpDropLabel()); + } + + @Override + public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt) + throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), tgt); + } + /** * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java index 045423b..07bd191 100644 --- a/src/android/net/apf/ApfV6Generator.java +++ b/src/android/net/apf/ApfV6Generator.java
@@ -262,6 +262,18 @@ } @Override + public ApfV6Generator addJumpIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt); + } + + @Override + public ApfV6Generator addJumpIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(bytesList, tgt); + } + + @Override public ApfV6Generator addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) {
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java index 68a29c5..90f0a28 100644 --- a/src/android/net/apf/ApfV6GeneratorBase.java +++ b/src/android/net/apf/ApfV6GeneratorBase.java
@@ -552,6 +552,21 @@ return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, false /* jumpOnMatch */); } + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesSet}. + */ + public abstract Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset, + @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match none of the elements in + * {@code bytesSet}. + */ + public abstract Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset, + @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException; /** * Check if the byte is valid dns character: A-Z,0-9,-,_,%,@
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt index 2d20ed6..6fcf641 100644 --- a/tests/unit/src/android/net/apf/ApfFilterTest.kt +++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -22,6 +22,8 @@ import android.net.MacAddress import android.net.NattKeepalivePacketDataParcelable import android.net.TcpKeepalivePacketDataParcelable +import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V4_MAC_ADDRESS +import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V6_MAC_ADDRESS import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4 import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST @@ -240,6 +242,8 @@ intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(), // 33:33:ff:bb:cc:dd intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(), + ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, + ETH_MULTICAST_MDNS_V6_MAC_ADDRESS ) // Using scapy to generate payload: @@ -5797,7 +5801,8 @@ ) assertThat(program.size).isLessThan(apfRamSize + 1) assertThat(program).isNotEqualTo(ByteArray(apfRamSize) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64) apfRamSize += step } } @@ -5817,7 +5822,8 @@ val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize() assertThat(program.size).isLessThan(availableRam + 1) assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64) apfRamSize += step } } @@ -5836,7 +5842,8 @@ val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize() assertThat(program.size).isLessThan(availableRam + 1) assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64) apfRamSize += step } }